import math
import time
import datetime
class ICalcWaitTime(object):
    def __init__(self, trans, timespan, interval):
        self.__trans = int(trans)
        self.__timespan = timespan
        self.__interval = interval
        self.__tsStart = datetime.datetime.now()

    def getTrans(self):
        return self.__trans
    def getTimespan(self):
        return self.__timespan
    def getInterval(self):
        return self.__interval
    def getWorkLoadSpan(self):
        return self.__workLoadSpan

    def workLoadStart(self):
        self.__tsStart = datetime.datetime.now()
    def getCurrentTrans(self):
        pass
    def workLoadEnd(self):
        return (datetime.datetime.now() - self.__tsStart).total_seconds()

    def getWaitTime(self):
        pass

    def __str__(self):
        return "trans:%d,timespan:%f,interval:%f" % (self.__trans, self.__timespan, self.__interval)

#timespan == interval
class CalcWaitTimeInterval(ICalcWaitTime):
    def __init__(self, trans, timespan):
        super(CalcWaitTimeInterval, self).__init__(trans, timespan ,timespan)

    def getCurrentTrans(self):
        return math.ceil(self.getTrans())

    def workLoadStart(self):
        super(CalcWaitTimeInterval, self).workLoadStart()

    def getWaitTime(self):
        workLoadSpan = super(CalcWaitTimeInterval, self).workLoadEnd()
        if workLoadSpan <= self.getInterval():
            return self.getInterval() - workLoadSpan
        else: #workLoadSpan > interval
            return 0.0
    def __str__(self):
        return super(CalcWaitTimeInterval, self).__str__()
        
class CalcWaitTimeMultiInterval(ICalcWaitTime):
    def __init__(self, trans, timespan, interval):
        assert timespan > interval
        super(CalcWaitTimeMultiInterval, self).__init__(trans, timespan ,interval)
        self.__numberOfRound = int(math.ceil(timespan/interval)) #readonly
        assert self.__numberOfRound > 0

        self.__totalTrans = int(trans)
        assert self.__totalTrans > 0

        self.__leftTrans = self.__totalTrans
        self.__transPerInterval = int(math.ceil(trans * interval/timespan))     #readonly
        assert self.__transPerInterval > 0

        self.__curNumberOfRound = self.__numberOfRound

    def __str__(self):
        return super(CalcWaitTimeMultiInterval, self).__str__() +  " rounds:%d, totalTrans:%d, transPerInterval:%d, curNoRound:%d, leftTrans:%d" % (
            self.__numberOfRound, self.__totalTrans, self.__transPerInterval
            , self.__curNumberOfRound, self.__leftTrans)

    def getCurrentTrans(self):
        self.__cur = 0
        if self.__leftTrans >= self.__transPerInterval:
            self.__cur = self.__transPerInterval
            self.__leftTrans = self.__leftTrans - self.__transPerInterval
        else:
            self.__cur = self.__leftTrans
            self.__leftTrans = 0

        self.__curNumberOfRound = self.__curNumberOfRound - 1
        if self.__curNumberOfRound == 0:
            self.__leftTrans  = self.__totalTrans
            self.__curNumberOfRound = self.__numberOfRound
        return self.__cur

    def getWaitTime(self):
        #leftTrans consumer to 0 already =>need have a rest
        if self.__leftTrans == 0 and self.__curNumberOfRound > 0 and self.__cur == 0:
            return self.getInterval()

        workLoadSpan = super(CalcWaitTimeMultiInterval, self).workLoadEnd()
        if self.getInterval() >= workLoadSpan:
            return self.getInterval() - workLoadSpan
        else:
            print("Warning: workLoadSpan(%f) > Interval(%f) in round:%d" % (workLoadSpan, self.getInterval(), self.__curNumberOfRound), "will reset this cycle, [Why:maybe increase the interval]")
            #reset the status
            self.__leftTrans = self.__totalTrans
            self.__curNumberOfRound = self.__numberOfRound
            return 0.0

    def workLoadStart(self):
        super(CalcWaitTimeMultiInterval, self).workLoadStart()

class CalcWaitTime:
    def __init__(self, trans, timespan, interval=0.1):
        assert interval > 0.0 and timespan > 0.0 and trans > 0
        if timespan > interval:
            self.__calc = CalcWaitTimeMultiInterval(trans, timespan, interval)
        elif timespan == interval:
            self.__calc  = CalcWaitTimeInterval(trans, timespan)
        else: # timspan < interval
            self.__calc = CalcWaitTimeInterval(trans*interval/timespan, interval)

    def getTrans(self):
        return self.__calc.getTrans()
    def getTimespan(self):
        return self.__calc.getTimespan()
    def getInterval(self):
        return self.__calc.getInterval()

    def workLoadStart(self):
        self.__calc.workLoadStart()

    def getCurrentTrans(self):
        return self.__calc.getCurrentTrans()

    def workLoadEnd(self):
        return self.__calc.getWaitTime()

    def __str__(self):
        return str(self.__calc)

import unittest
class TestCalcWaitTime(unittest.TestCase):
    # interval == timespan; workLoad <= interval
    def testWorkLoadLessThanThanInterval(self):
        calc = CalcWaitTime(5, 0.5, 0.5)
        print(str(calc))
        calc.workLoadStart()
        curTrans = calc.getCurrentTrans()
        print("curTrans:", curTrans)
        self.assertEqual(curTrans, 5)
        print("sleep(0.3)")
        time.sleep(0.3) #workload
        wait = calc.workLoadEnd()
        print("wait timeout :", wait)
        self.assertTrue(wait < 0.21)

    # interval == timespan; workLoad > interval 
    def testWorkloadBiggerThanInterval(self): 
        calc = CalcWaitTime(5, 0.5, 0.5)
        print(str(calc))
        calc.workLoadStart()
        self.assertEqual(calc.getCurrentTrans(), 5)
        print("sleep(1)")
        time.sleep(1) #workload
        self.assertTrue(calc.workLoadEnd() < 0.0001)

    #timespan > interval; workLoad <= interval
    def testWorkLoad(self):
        calc = CalcWaitTime(5, 2, 0.5)
        rounds = math.ceil(calc.getTimespan()/calc.getInterval())
        for i in range(int(rounds)):
            print(calc)
            calc.workLoadStart()

            curTrans = calc.getCurrentTrans()
            print("curTrans:%d" % curTrans)
            if curTrans > 0:
                print("sleep(0.3)")
                time.sleep(0.3) #workLoad

            wait = calc.workLoadEnd()
            print("wait time:%f" % wait)

    # timespan < interval
    def testWorkLoadLongInterval(self):
        calc = CalcWaitTime(5.3, 1.3, 5.7)
        print(str(calc))
        calc.workLoadStart()
        print("curTrans:", calc.getCurrentTrans())
        print("sleep(3)")
        time.sleep(3) #workload
        print("wait :", calc.workLoadEnd())



if __name__ == "__main__":
    suite = unittest.TestSuite([
          unittest.TestLoader().loadTestsFromTestCase(TestCalcWaitTime),
              ])
    unittest.TextTestRunner(verbosity=2).run(suite)
